﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ADDDF.Infrastructure.Collections;

namespace ADDDF.Infrastructure.MemoryBound
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public abstract class MBRepositoryBase<T> : RepositoryBase<T>
        where T : IAggregateRoot
    {
        protected class EntitySetConfig
        {
            public IEnumerable<IEntity> DefaultData { get; set; }
            public int IntegerKeyInitialValue { get; set; }
        }

        #region Constructors

        protected MBRepositoryBase()
            : base()
        {
        }

        #endregion

        #region MBRepositoryBase Methods

        public void RemoveAll()
        {
            GetEntitySet().RemoveAll();
        }

        #endregion

        #region RepositoryBase<T> Implementations

        public override T FindByKey(object key)
        {
            EnsureEntitySetWasCreated();

            var entitySet = GetEntitySet();

            T found = (T) entitySet.FindByKeyV(key);

            if (found == null)
                return default(T);
            else
                return InternalCloneAggregateRoot(found);
        }

        public override IEnumerable<T> FindAll()
        {
            EnsureEntitySetWasCreated();

            var entitySet = GetEntitySet();

            var all = entitySet.GetAllV();

            // Need this because FindAll returns IEnumerable<IEntity> and
            // it cannot be implicitly converted to IEnumerable<T>.
            return all.Select(x => InternalCloneAggregateRoot((T) x));
        }

        public override void Add(T item)
        {
            EnsureEntitySetWasCreated();

            IAllowKeyAssignment keyAssignable = item as IAllowKeyAssignment;

            if (keyAssignable != null)
                keyAssignable.AssignKey(InternalGetNewKey(item));

            T cloned = InternalCloneAggregateRoot(item);

            GetEntitySet().AddAndReplaceV(cloned);
        }

        public override void Change(T item)
        {
            EnsureEntitySetWasCreated();

            T cloned = InternalCloneAggregateRoot(item);

            GetEntitySet().AddAndReplaceV(cloned);
        }

        public override void Remove(T item)
        {
            EnsureEntitySetWasCreated();

            GetEntitySet().Remove(item);
        }

        #endregion

        #region EntitySet creation methods

        /// <summary>
        /// Create EntitySet for aggregate root if it is not already created.
        /// </summary>
        protected void EnsureEntitySetWasCreated()
        {
            EnsureEntitySetWasCreated(typeof(T));
        }

        /// <summary>
        /// Create EntitySet for a type if it is not already created.
        /// </summary>
        /// <param name="entityType">Any type</param>
        protected void EnsureEntitySetWasCreated(Type entityType)
        {
            // TODO: Try not to lock the whole MBDataSource?
            lock (MBDataStore.ForLock)
            {
                string entitySetName = entityType.FullName;

                if (MBDataStore.HasEntitySet(entitySetName))
                    return;

                EntitySetConfig esConfig = new EntitySetConfig
                {
                    DefaultData = null,
                    IntegerKeyInitialValue = 1
                };

                GetEntitySetConfig(entityType, esConfig);

                MBDataStore.SetNextIntKey(entitySetName, esConfig.IntegerKeyInitialValue);

                if (entityType.Equals(typeof(T)))
                {
                    MBDataStore.AddEntitySet(entitySetName,
                        esConfig.DefaultData,
                        entity => { return InternalCloneAggregateRoot((T)entity); });
                }
                else
                {
                    MBDataStore.AddEntitySet(entitySetName,
                        esConfig.DefaultData,
                        entity => { return InternalCloneEntity(entity); });
                }
            }
        }

        /// <summary>
        /// Derived class can override this to provide configurations for entity-set.
        /// </summary>
        /// <param name="esConfig"></param>
        protected virtual void GetEntitySetConfig(Type entityType, EntitySetConfig esConfig)
        {
            // Default do nothing
        }

        #endregion

        #region EntitySet access methods

        /// <summary>
        /// Returns an EntitySet for the aggregate root entity of repository.
        /// This function calls GetEntitySetByType.
        /// </summary>
        protected IEntityValueSet<IEntity> GetEntitySet()
        {
            return GetEntitySetByType(typeof(T));
        }

        /// <summary>
        /// Returns an EntitySet using the full-name of a given entity type object.
        /// If the EntitySet does not exists, it will be created.
        /// </summary>
        /// <param name="entityType"></param>
        /// <returns></returns>
        protected IEntityValueSet<IEntity> GetEntitySetByType(Type entityType)
        {
            string entitySetName = entityType.FullName;

            try
            {
                return MBDataStore.GetEntitySet(entitySetName);
            }
            catch (KeyNotFoundException ex)
            {
                throw new KeyNotFoundException("Entity set '" + entitySetName + "' not found. "
                    + "Make sure EnsureEntitySetWasCreated() was called", ex);
            }
        }

        #endregion

        #region Clone aggregate root entity

        /// <summary>
        /// Override this to return a copy of an aggregate root entity. 
        /// </summary>
        /// <param name="item">An aggregate root entity</param>
        /// <returns>A copy</returns>
        protected virtual T CloneAggregateRoot(T item) 
        {
            return default(T);
        }

        /// <summary>
        /// Clone entity. If derived class implements CloneEntity(), it will get used,
        /// otherwise, this attempts to use ICloneable of the entity. If the entity does 
        /// not implement ICloneable it simply returns the item.
        /// </summary>
        protected T InternalCloneAggregateRoot(T item)
        {
            object cloned = null;

            // Use CloneEntity if it will overriden by derived class.
            cloned = CloneAggregateRoot(item);

            if (cloned != null)
                return (T)cloned;

            // ICloneable interface takes priority
            ICloneable cloneable = item as ICloneable;

            if (cloneable != null)
                return (T)cloneable.Clone();

            // No way to clone, return the same instance instead.
            return item;
        }

        #endregion

        #region Clone non-aggregate root entity

        protected virtual IEntity CloneNonAggregateRootEntity(IEntity item)
        {
            return null;
        }

        protected IEntity InternalCloneEntity(IEntity item)
        {
            object cloned = null;

            // Use CloneEntity if it will overriden by derived class.
            cloned = CloneNonAggregateRootEntity(item);

            if (cloned != null)
                return (IEntity)cloned;

            // ICloneable interface takes priority
            ICloneable cloneable = item as ICloneable;

            if (cloneable != null)
                return (IEntity)cloneable.Clone();

            // No way to clone, return the same instance instead.
            return item;
        }
     
        #endregion

        #region Getting next key for aggregate root entity

        /// <summary>
        /// Override this to return a new key for an aggregate root entity.
        /// If not overriden, the default is to get the next integer key.
        /// </summary>
        /// <param name="entity">An aggregate root entity</param>
        /// <returns>A new key</returns>
        protected virtual object GetNewKey(T entity)
        {
            return null;
        }

        /// <summary>
        /// Return the next integer key for an aggregate root entity.
        /// </summary>
        /// <returns>Next integer key</returns>
        protected int GetNextIntegerKey()
        {
            return MBDataStore.GetNextIntKey(typeof(T).FullName);
        }

        /// <summary>
        /// Internal logic for obtaining a new key for an aggregate root entity. 
        /// Derived class should call this to match the default behavior.
        /// </summary>
        /// <param name="entity">An aggregate root entity</param>
        /// <returns>A new key</returns>
        protected object InternalGetNewKey(T entity)
        {
            object key = null;

            // Use GetNewKey if it implemented by derived class.
            key = GetNewKey(entity);

            if (key != null)
                return key;

            // Otherwise return next integer key.
            return GetNextIntegerKey();
        }

        #endregion        

        #region Getting next key for non-aggregate root entity

        /// <summary>
        /// Override this to return a new key for an non-aggregate root entity.
        /// If not overriden, the default is to get the next integer key.
        /// </summary>
        /// <param name="entity">A non-aggregate root entity</param>
        /// <returns>A new key</returns>
        protected virtual object GetNewKeyForEntity(IEntity entity)
        {
            return null;
        }

        /// <summary>
        /// Return the next integer key for a non-aggregate root entity.
        /// </summary>
        /// <param name="entity">A non-aggregate root entity</param>
        /// <returns>Next integer key</returns>
        protected int GetNextIntegerKeyForEntity(IEntity entity)
        {
            return MBDataStore.GetNextIntKey(entity.GetType().FullName);
        }

        /// <summary>
        /// Internal logic for obtaining a new key for a non-aggregate root entity. 
        /// Derived class should call this to match the default behavior.
        /// </summary>
        /// <param name="entity">A non-aggregate root entity</param>
        /// <returns>A new key</returns>
        protected object InternalGetNewKeyForEntity(IEntity entity)
        {
            object key = null;

            // Use GetNewKey if it implemented by derived class.
            key = GetNewKeyForEntity(entity);

            if (key != null)
                return key;

            // Otherwise return next integer key.
            return GetNextIntegerKey();
        }

        #endregion
    }
}
